-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Signal in rsutils #12228
Signal in rsutils #12228
Conversation
fix test-signal stress time measurement
|
||
// Returns the deferred function so you can move it somewhere else | ||
fn && detach() { return std::move( _deferred ); } | ||
fn detach() { return std::move( _deferred ); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about the state of _deferred
after the move. Acording to https://stackoverflow.com/questions/13680587/move-semantic-with-stdfunction it will be in an unspecified state, that might not be empty.
If subscription is detach
ed and then cancel
ed it might call the detached funciton?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It says "Unless otherwise specified" -- meaning if a move constructor or operator was not defined. In the case of std::function, it is defined. It is true that, up to C++20, it is not guaranteed to be valid. But this is tested in the unit-test. And between us, we know it resets to nothing...
void _Reset_move(_Func_class&& _Right) noexcept { // move _Right's stored object
if (!_Right._Empty()) {
if (_Right._Local()) { // move and tidy
_Set(_Right._Getimpl()->_Move(&_Mystorage));
_Right._Tidy();
} else { // steal from _Right
_Set(_Right._Getimpl());
_Right._Set(nullptr);
}
}
}
function(function&& _Right) noexcept {
this->_Reset_move(_STD move(_Right));
}
Anyway, if this is a problem then it's a problem in deferred
which was there before too. Let's talk?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If subscription is
detach
ed and thencancel
ed it might call the detached funciton?
A detach will do _d.detach()
, meaning _d
should now be nothing. If you then cancel()
then _d
is nothing and nothing should happen. Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make sure this is covered in the unit-test.
add detach() followed by cancel()
// | ||
// Commonly used with signal. | ||
// | ||
class subscription |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a "notify" method, enabling calling the deffered function not only on destruction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what cancel()
is... right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cancel()
is one time only because it detaches the function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You want to call it multiple times?
Then you can detach and store it elsewhere. The whole point of a subscription is that the unsubscribe action happens once and only once...
subscription_slot add( callback && func ) | ||
{ | ||
std::lock_guard< std::mutex > locker( _impl->mutex ); | ||
// NOTE: we should maintain ordering of subscribers: later subscriptions should be called after earlier ones, so |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we want to keep order of calls? We should not rely on it when using a general utility.
We might not be able to guarantee this if moving to other implementations, e.g. boost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's general-purpose is exactly why I wanted to keep the order.
If the order is non-deterministic, then the function (result of using all the callbacks) isn't deterministic. E.g., the unit-test actually has a callback that gets an std::string &
argument -- it actually changes the output value. The caller decides the functionality of the callbacks, and therefore losing determinism is not a good thing IMO. It doesn't cost much, either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might not be able to guarantee this if moving to other implementations, e.g. boost.
That's true. I actually implemented using vector<>
. It was faster to create and run the signals. And you're right, it would have been harder to make it deterministic, though possible. The problem was the complexity. In the end I chose to go back to a map<>
implementation because of its straightforwardness. It's good enough for our needs.
We're not likely to change the implementation. And if we do, it's better if we ensure determinism then, too.
|
||
// Unlike add(), subscribing implies a subscription, meaning the caller needs to store the return value or else it | ||
// will get unsubscribed immediately!! | ||
subscription subscribe( callback && func ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you subscribe callbacks that receive arguments? subscription
is using defered::fn
which does not receive any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the beauty of it: a subscription doesn't have to know anything about what it's unsubscribing. It doesn't even have to work with signal
-- you can have anything return a subscription
.
The "callback" that goes into a subscription is always without arguments -- it has to be called from a destructor. if you want to pass stuff in, you do it thru the lambda, like signal
does it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I get it now.
Maybe the name subscription
is not the best, as it is not like a subscription to a publisher. It is only a one time call to a function, suitable to be used as a deleter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, a subscription
is just a deferred
function but with different semantics: one main difference is the operator=()
calling cancel()
.
Think of it like a subscription in English, not a subscription in the context of publisher-subscriber.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not too sure about what are the benefits of using a subscription
over a deferred
function though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The semantics.
Deferred will not cancel()
on assignment.
Deferred has no cancel()
.
deferred
is a good utility to have. subscription
is a use-case with its own set of requirements..
list->dev, | ||
&list->dev.device->get_sensor(index) | ||
list->device, | ||
&list->device->get_sensor(index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that list->device
is a shared_ptr and not an object shouldn't we test that is is not empty before dereferencing?
Same for other places in the file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a shared_ptr before, too -- just contained inside list->dev.device
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
types.h
torsutils/signal.h
std::forward
!)deferred::detach()
public_signal
usecase from basesignal
class+=
,-=
,()
)add()
andremove()
when manually dealing with slots, and justsubscribe()
when dealing withsubscription
objects that automatically clean uprs2_device
is removed, and overriding playback-status-changed callbacks will actually remove the previousTracked on [LRS-890]